"""
处理请求相关和生成测试报告
"""
import re
import json
from io import BytesIO
import datetime
import unittest

import requests
from deepdiff import DeepDiff
from jsonpath_rw import parse

from HTMLTestRunner import HTMLTestRunner

from apps.app01 import models
from utils.Construct_test_cases import ApiModelData


class MyCase(unittest.TestCase):
    def test_case_equal(self):
        """用例"""
        self._testMethodDoc = "用例名:{0} | 用例标题:{1} ".format(self.api_name,self.api_title)  # 自定义测试报告中的用例描述
        self.assertEqual(DeepDiff(self.response, self.expect).get("values_changed", None), None, msg=self.msg)


class RequestOperate:

    def __init__(self, current_case, all_api_list, suite_list, api_model_obj):
        self.current_case_obj = current_case  # 当前执行的测试用例数据
        self.all_api_list = all_api_list  # 数据库中用例表中所有测试用例,列表嵌套字典格式
        self.suite_list = suite_list  # 单元测试框架suite_list对象
        self.api_model_obj = api_model_obj  # 所有用例表对象

    def handler(self):
        """
        关于请求的一些列流程
        1、提取case_obj对象中的字段值，使用requests发送请求
            对请求参数进行校验
            将请求结果提取出来
        2、使用unittest进行断言
        3、更新数据库字段
        4、将执行结果添加到日志表中
        5、前端返回
        """
        # 发请求，断言，并生成测试报告
        self.send_msg()

    def send_msg(self):
        """
        发请求
        :return:
        """
        try:
            response = requests.request(
                method="{0}".format(self.current_case_obj.get_api_method_display()),
                url=self.current_case_obj.api_url,
                data=self._check_request_data(),
                params=self._check_request_params(),
                json=self._check_request_json(),
                cookies=self._check_request_cookies(),
                headers=self._check_request_headers(),
            )
            self._write_cookies(response)
            self.assert_msg(response.json())
        except Exception as error:
            pass

    def _write_cookies(self, response):
        """
        监测响应结果中是否含有cookies，有就需要保存起来
        :param response:
        :return:
        """
        for item in self.all_api_list:
            if item.get("api_name") == self.current_case_obj.api_name:
                item["api_temporary_response_cookies"] = json.dumps(response.cookies.get_dict())
                if response.headers.get("Content-Type").lower() == "application/json":
                    item["api_temporary_response_json"] = json.dumps(response.json())
                # 所有请求头中获取数据都存一份
                item["api_temporary_request_headers"] = self.current_case_obj.api_headers
                item["api_temporary_request_data"] = self.current_case_obj.api_data
                item["api_temporary_request_json"] = self.current_case_obj.api_json
                item["api_temporary_request_params"] = self.current_case_obj.api_params

                # 响应头都存一份
                headers = dict(response.headers)
                item["api_temporary_response_headers"] = json.dumps(headers)

                self.api_model_obj.filter(api_name=self.current_case_obj.api_name).update(**item)

    def assert_msg(self, response):
        """
        处理断言
        :return:
        """
        case = MyCase(methodName="test_case_equal")
        case.response = response #  响应json格式数据
        case.expect = self._check_expect()
        case.msg = "自定义的错误信息:{0}".format(DeepDiff(response, self._check_expect()))
        case.api_name = self.current_case_obj.api_name
        case.api_title = self.current_case_obj.api_title
        case.desc = self.current_case_obj.api_desc
        self.suite_list.addTest(case)
        suite = unittest.TestSuite()# 单用例执行时生成测试报告
        suite.addTest(case)
        self.create_single_report(suite)

    def create_single_report(self, suite):
        """
        生成单个用例报告
        :param suite:
        :return:
        """
        f = BytesIO()
        result = HTMLTestRunner(
            stream=f,
            # verbosity=2,
            title=self.current_case_obj.api_name,
            description=self.current_case_obj.api_desc,
        ).run(suite)
        self.update_api_status(result, f)

    def update_api_status(self, result, f):
        """
        更新数据库相关字段的状态
            1、api_report
            2、api_run_time
            3、api_pass_status
            4、api_run_status
        """
        # 写报告
        obj = models.Api.objects.filter(pk=self.current_case_obj.pk).first()
        obj.api_report = f.getvalue().decode()  # 读取报告然后保存到测试平台的api_report字段中
        # 写执行时间
        obj.api_run_time = datetime.datetime.now()
        # 写是否执行
        obj.api_run_status = 1
        # 写api_pass_status
        for i in result.__dict__['result']:
            if i[0]:  # 用例执行失败
                obj.api_pass_status = 0
            else:
                obj.api_pass_status = 1
        obj.save()

    def create_m_report(self, suite):
        """
        生成批量用例报告
        :param suite: 用例集
        :return:
        """
        f = BytesIO()
        result = HTMLTestRunner(
            stream=f,
            # verbosity=2,
            title=self.current_case_obj.api_name,
            description=self.current_case_obj.api_desc,
        ).run(suite)
        self.update_log_status(result, f)

    def update_log_status(self, result, f):
        """
        更新数据库相关字段的状态
        1、api_report
        2、api_run_time
        3、api_pass_status
        4、api_run_status
        """
        # 写log表，通过多少，失败多少，共执行了多少用例
        models.Logs.objects.create(
            log_report=f.getvalue().decode(),
            log_sub_it_id=self.current_case_obj.api_sub_it_id,
            log_pass_count=result.__dict__["success_count"],
            log_errors_count=result.__dict__["error_count"],
            log_failed_count=result.__dict__["failure_count"],
            log_run_count=result.__dict__["testsRun"]
        )

    def _check_request_data(self):
        """
        校验请求的data参数
            默认数据库中的data字段是标准的json格式
        :return:
        """
        data = self.current_case_obj.api_data
        if data:
            return self._operate_re_msg(data)
        else:
            return {}

    def _check_request_params(self):
        """
        校验请求的params参数
        :return:
        """
        api_params = self.current_case_obj.api_params
        if api_params:
            return json.loads(api_params)
        else:
            return {}

    def _check_request_cookies(self):
        """
        处理请求中的cookies
        :return:
        """
        cookies_case_name = self.current_case_obj.api_cookies
        if cookies_case_name:
            for item in self.all_api_list:
                if item.get("api_name") == cookies_case_name:
                    res_cookies = item.get("api_temporary_response_cookies", {})
                    return json.loads(res_cookies)
        else:
            return {}

    def _check_request_json(self):
        """
        处理请求json
        :return:
        """
        api_json = self.current_case_obj.api_json
        if api_json:
            return json.loads(api_json)
        else:
            return {}

    def _check_request_headers(self):
        """
        校验请求头，做携带cookies 和 数据依赖的问题
        {
            'user':'${}$',
            'testfan-id':'ca447223-876e-46ba-9e45-f775335dfcbe'
        }

        :return:
        """
        headers = self.current_case_obj.api_headers
        if headers:
            return self._operate_re_msg(headers)
        else:
            return {}

    def _check_expect(self):
        """处理预期值"""
        if self.current_case_obj.api_expect:
            # 如果预期值写错key不是code，直接返回不通过
            api_expect = json.loads(self.current_case_obj.api_expect)
            for key in api_expect:
                if key == "code":
                    api_expect['code'] = str(api_expect["code"])
                    return api_expect
            else:
                return {"code": "333"}
        else:
            return {}

    def _operate_re_msg(self, parameter):
        """
        正则校验,数据依赖的字段
        :param parameter: 各种参数，如：data,header,params
        :return:
        """
        # 使用re 将提取依赖字段 {"testfan-token":"${neeo_001>response_json>data}$"}
        if isinstance(parameter, dict):
            json.dumps(parameter)
        pattern = re.compile("\${(.*?)}\$")  # 定义规则
        rule_list = pattern.findall(parameter)  # 按照规则匹配
        if rule_list:  # 该参数有数据依赖要处理
            for rule in rule_list:
                case_num, params, json_path = rule.split(">")
                for line in self.all_api_list:
                    if line.get("api_name") == case_num:
                        temp_data = line.get("{0}".format(params)) # api_temporary_response_json ,params就是response_json
                        if isinstance(temp_data, str):
                            temp_data = json.loads(temp_data)
                        match_list = parse(json_path).find(temp_data)# json_path=data,temp_data=响应的json数据，key=data,保存在数据库中的值。
                        # 使用data字段去字典temp_data中匹配key=data的值
                        if match_list:
                            match_data = [v.value for v in match_list][0]
                            # 将提取出来的值替换到原来规则,
                            parameter = re.sub(pattern=pattern, repl=match_data, string=parameter, count=1)
            return json.loads(parameter)
        else:
            if isinstance(parameter, str):
                parameter = json.loads(parameter)
            return parameter


def run_case(current_api_list):
    suite_list = unittest.TestSuite()  # 创建suite容器
    api_obj = ApiModelData()  # 实例化数据库中所有测试用例对象
    all_api_list = api_obj.get_all_test_case  # 获取数据库中所有测试用例，格式列表中嵌套字典
    for item in current_api_list:
        RequestOperate(current_case=item, suite_list=suite_list, all_api_list=all_api_list,
                       api_model_obj=api_obj.api_obj).handler()
    RequestOperate(current_case=item, suite_list=suite_list, all_api_list=all_api_list,
                   api_model_obj=api_obj.api_obj).create_m_report(suite_list)
